﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections.Concurrent;
using System.Collections.Generic;
using Couchbase;
using GuoZhiQi.MiddleCache.Common;
using Enyim.Caching.Memcached.Results;
using Enyim.Caching.Memcached;

namespace GuoZhiQi.MiddleCache
{
    public static class CouchBaseHelper
    {
        private static readonly char[] ForbiddenChars = {
            '\u0000', '\u0001', '\u0002', '\u0003',
            '\u0004', '\u0005', '\u0006', '\u0007',
            '\u0008', '\u0009', '\u000a', '\u000b',
            '\u000c', '\u000d', '\u000e', '\u000f',
            '\u0010', '\u0011', '\u0012', '\u0013',
            '\u0014', '\u0015', '\u0016', '\u0017',
            '\u0018', '\u0019', '\u001a', '\u001b',
            '\u001c', '\u001d', '\u001e', '\u001f',
            '\u0020'
        };
        /// <summary>
        /// 检测所有的key并且替换不合法的key为-
        /// </summary>
        /// <param name="originalKey"></param>
        /// <returns></returns>
        public static string ResolveCouchBaseKeyPolicy(string originalKey)
        {
            int badKeyIndex = originalKey.IndexOfAny(ForbiddenChars);
            while (badKeyIndex > -1)
            {
                originalKey = originalKey.Remove(badKeyIndex,1);
                originalKey = originalKey.Insert(badKeyIndex, "-");
                badKeyIndex = originalKey.IndexOfAny(ForbiddenChars);
            }
            return originalKey;
        }

        public static ConcurrentBag<T> GetMany<T>(List<string> keys, CouchbaseClient client, Func<CouchbaseClient, T> creationMethod)
              where T : CouchBaseObject
        {
            IDictionary<string, object> items = client.Get(keys);
            if (items.Count == 0)
            {
                return new ConcurrentBag<T>();
            }
            ConcurrentBag<T> couchbaseObjects = new ConcurrentBag<T>();
            foreach (var pair in items)
            {
                object jsonValue = pair.Value;
                if (jsonValue != null)
                {
                    CouchBaseObject couchBaseItem = creationMethod(client);
                    couchBaseItem.FromJson(jsonValue.ToString());
                    couchBaseItem.Key = pair.Key;
                    couchBaseItem._client = client;

                    couchbaseObjects.Add((T)couchBaseItem);
                }
            }
            return couchbaseObjects;
        }

        /// <summary>
        /// Locks properties and tries to set their values, while allowing other properties to be updated.
        /// </summary>
        /// <param name="item">Membase object to set.</param>
        /// <param name="RetriesCount">Number of retries before it fails.</param>
        /// <param name="LockedProperties">Properties which must be locked, e.g. overwritten.</param>
        public static void LockAndSet(CouchBaseObject item, int RetriesCount = 10, params string[] LockedProperties)
        {
            int tries = 0;

            Dictionary<string, object> props = new Dictionary<string, object>();
            foreach (string propName in LockedProperties)
            {
                props.Add(propName, ReflectionUtil.GetPublicPropertyValue(item, propName));
            }

            IStoreOperationResult result = item.Client.ExecuteCas(StoreMode.Set, item.Key, item.ToJson(), item.CAS);

            while (!result.Success && tries < RetriesCount)
            {
                if (!item.Refresh())
                    throw new NullReferenceException("Item removed from the database or unable to refresh the data.");

                foreach (string propName in props.Keys)
                {
                    ReflectionUtil.TrySetPropertyValue(item, propName, props[propName]);
                }

                result = item.Client.ExecuteCas(StoreMode.Set, item.Key, item.ToJson(), item.CAS);
                tries++;
            }

            if (tries >= RetriesCount)
                throw new OperationCanceledException("Max retries reached. Update failed for key " + item.Key);
        }

        /// <summary>
        /// Increments property value.
        /// </summary>
        /// <param name="item">Object that contains property to increment.</param>
        /// <param name="IntPropName">Integer property name to increment.</param>
        /// <param name="RetriesCount">Number of retries before it fails.</param>
        public static void Increment(CouchBaseObject item, string IntPropName, int RetriesCount = 10)
        {
            int tries = 0;
            int propValue = (int)ReflectionUtil.GetPublicPropertyValue(item, IntPropName);
            propValue++;

            ReflectionUtil.TrySetPropertyValue(item, IntPropName, propValue);

            IStoreOperationResult result = item.Client.ExecuteCas(StoreMode.Set, item.Key, item.ToJson(), item.CAS);

            while (!result.Success && tries < RetriesCount)
            {
                if (!item.Refresh())
                    throw new NullReferenceException("Item removed from the database or unable to refresh the data.");

                propValue = (int)ReflectionUtil.GetPublicPropertyValue(item, IntPropName);
                propValue++;

                result = item.Client.ExecuteCas(StoreMode.Set, item.Key, item.ToJson(), item.CAS);
                tries++;
            }

            if (tries >= RetriesCount)
                throw new OperationCanceledException("Max retries reached. Update failed for key " + item.Key);
        }

        /// <summary>
        /// Decrements property value.
        /// </summary>
        /// <param name="item">Object that contains property to increment.</param>
        /// <param name="IntPropName">Integer property name to increment.</param>
        /// <param name="RetriesCount">Number of retries before it fails.</param>
        public static void Decrement(CouchBaseObject item, string IntPropName, int RetriesCount = 10)
        {
            int tries = 0;
            int propValue = (int)ReflectionUtil.GetPublicPropertyValue(item, IntPropName);
            propValue--;

            ReflectionUtil.TrySetPropertyValue(item, IntPropName, propValue);

            IStoreOperationResult result = item.Client.ExecuteCas(StoreMode.Set, item.Key, item.ToJson(), item.CAS);

            while (!result.Success && tries < RetriesCount)
            {
                if (!item.Refresh())
                    throw new NullReferenceException("Item removed from the database or unable to refresh the data.");

                propValue = (int)ReflectionUtil.GetPublicPropertyValue(item, IntPropName);
                propValue--;

                result = item.Client.ExecuteCas(StoreMode.Set, item.Key, item.ToJson(), item.CAS);
                tries++;
            }

            if (tries >= RetriesCount)
                throw new OperationCanceledException("Max retries reached. Update failed for key " + item.Key);
        }

        /// <summary>
        /// Locks properties and tries to set their values, while allowing other properties to be updated asynchronously.
        /// </summary>
        /// <param name="item">Membase object to set.</param>
        /// <param name="RetriesCount">Number of retries before it fails.</param>
        /// <param name="LockedProperties">Properties which must be locked, e.g. overwritten.</param>
        public static void LockAndSetAsync(CouchBaseObject item, int RetriesCount = 10, params string[] LockedProperties)
        {
            int tries = 0;

            Dictionary<string, object> props = new Dictionary<string, object>();
            foreach (string propName in LockedProperties)
            {
                props.Add(propName, ReflectionUtil.GetPublicPropertyValue(item, propName));
            }

            IStoreOperationResult result = item.Client.ExecuteCas(StoreMode.Set, item.Key, item.ToJson(), item.CAS);

            Task task = Task.Factory.StartNew(() =>
            {
                while (!result.Success && tries < RetriesCount)
                {
                    if (!item.Refresh())
                        throw new NullReferenceException("Item removed from the database or unable to refresh the data.");

                    foreach (string propName in props.Keys)
                    {
                        ReflectionUtil.TrySetPropertyValue(item, propName, props[propName]);
                    }

                    result = item.Client.ExecuteCas(StoreMode.Set, item.Key, item.ToJson(), item.CAS);
                    tries++;
                }

                if (tries >= RetriesCount)
                    throw new OperationCanceledException("Max retries reached. Update failed for key " + item.Key);
            });
        }

        /// <summary>
        /// Increments property value asynchronously.
        /// </summary>
        /// <param name="item">Object that contains property to increment.</param>
        /// <param name="IntPropName">Integer property name to increment.</param>
        /// <param name="RetriesCount">Number of retries before it fails.</param>
        public static void IncrementAsync(CouchBaseObject item, string IntPropName, int RetriesCount = 10)
        {
            int tries = 0;
            int propValue = (int)ReflectionUtil.GetPublicPropertyValue(item, IntPropName);
            propValue++;

            ReflectionUtil.TrySetPropertyValue(item, IntPropName, propValue);

            IStoreOperationResult result = item.Client.ExecuteCas(StoreMode.Set, item.Key, item.ToJson(), item.CAS);
            Task task = Task.Factory.StartNew(() =>
            {
                while (!result.Success && tries < RetriesCount)
                {
                    if (!item.Refresh())
                        throw new NullReferenceException("Item removed from the database or unable to refresh the data.");

                    propValue = (int)ReflectionUtil.GetPublicPropertyValue(item, IntPropName);
                    propValue++;

                    result = item.Client.ExecuteCas(StoreMode.Set, item.Key, item.ToJson(), item.CAS);
                    tries++;
                }

                if (tries >= RetriesCount)
                    throw new OperationCanceledException("Max retries reached. Update failed for key " + item.Key);
            });
        }

        /// <summary>
        /// Decrements property value asynchronously.
        /// </summary>
        /// <param name="item">Object that contains property to increment.</param>
        /// <param name="IntPropName">Integer property name to increment.</param>
        /// <param name="RetriesCount">Number of retries before it fails.</param>
        public static void DecrementAsync(CouchBaseObject item, string IntPropName, int RetriesCount = 10)
        {
            int tries = 0;
            int propValue = (int)ReflectionUtil.GetPublicPropertyValue(item, IntPropName);
            propValue--;

            ReflectionUtil.TrySetPropertyValue(item, IntPropName, propValue);

            IStoreOperationResult result = item.Client.ExecuteCas(StoreMode.Set, item.Key, item.ToJson(), item.CAS);

            Task task = Task.Factory.StartNew(() =>
            {
                while (!result.Success && tries < RetriesCount)
                {
                    if (!item.Refresh())
                        throw new NullReferenceException("Item removed from the database or unable to refresh the data.");

                    propValue = (int)ReflectionUtil.GetPublicPropertyValue(item, IntPropName);
                    propValue--;

                    result = item.Client.ExecuteCas(StoreMode.Set, item.Key, item.ToJson(), item.CAS);
                    tries++;
                }

                if (tries >= RetriesCount)
                    throw new OperationCanceledException("Max retries reached. Update failed for key " + item.Key);
            });
        }

    }
}
